package top.zsyle.basic.service.impl;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import top.zsyle.basic.config.BussinessException;
import top.zsyle.basic.constant.LoginConst;
import top.zsyle.basic.dto.BinderDto;
import top.zsyle.basic.dto.LoginCodeDto;
import top.zsyle.basic.dto.LoginDto;
import top.zsyle.basic.dto.WeChatDto;
import top.zsyle.basic.jwt.JwtUtils;
import top.zsyle.basic.jwt.RsaUtils;
import top.zsyle.basic.jwt.UserInfo;
import top.zsyle.basic.service.LoginService;
import top.zsyle.basic.util.AjaxResult;
import top.zsyle.basic.util.EmailUtil;
import top.zsyle.basic.util.HttpUtil;
import top.zsyle.basic.util.MD5Utils;
import top.zsyle.basic.vo.LoginVo;
import top.zsyle.org.domain.Employee;
import top.zsyle.org.domain.Shop;
import top.zsyle.org.mapper.EmployeeMapper;
import top.zsyle.org.mapper.ShopMapper;
import top.zsyle.system.domain.Menu;
import top.zsyle.system.mapper.MenuMapper;
import top.zsyle.system.mapper.PermissionMapper;
import top.zsyle.user.domain.Logininfo;
import top.zsyle.user.domain.User;
import top.zsyle.user.domain.Wxuser;
import top.zsyle.user.mapper.LogininfoMapper;
import top.zsyle.user.mapper.UserMapper;
import top.zsyle.user.mapper.WxuserMapper;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class LoginServiceImpl implements LoginService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private LogininfoMapper logininfoMapper;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private EmployeeMapper employeeMapper;

    @Autowired
    private ShopMapper shopMapper;

    @Value("${jwt.rsa.pri}")
    private String jwtRsaPri;

    @Value("${jwt.rsa.pub}")
    private String jwtRsaPub;

    /**
     * 账号密码登录业务
     *
     * @param dto
     * @return
     */
    @Override
    public HashMap<String, Object> account(LoginDto dto) {
        //1.校验参数
        //2.判断是否已经注册
        Logininfo logininfo = logininfoMapper.findByDto(dto);
        if (null == logininfo) {
            throw new BussinessException("账户或密码错误");
        }
        //3.判断密码是否一致
        String passwordInput = MD5Utils.encrypByMd5(logininfo.getSalt() + dto.getPassword());
        if (!logininfo.getPassword().equals(passwordInput)) {
            throw new BussinessException("密码错误");
        }
        //4.生成token并保存到redis中，设置默认时间为30分钟
        HashMap<String,Object> map = getMap(logininfo);
        //7.返回给前端
        return map;
    }

    /**
     * 短信验证码登录
     *
     * @param dto
     * @return
     */
    @Override
    public HashMap<String, Object> smscodeLogin(LoginCodeDto dto) {
        //1.校验参数
        //2.判断是否已经注册
        LoginDto loginDto = new LoginDto();
        loginDto.setType(1);
        loginDto.setAccount(dto.getPhone());
        Logininfo logininfo = logininfoMapper.findByDto(loginDto);
        if (null == logininfo) {
            throw new BussinessException("您的电话还没有注册");
        }
        //4.生成token并保存到redis中，设置默认时间为30分钟
        HashMap<String, Object> map = getMap(logininfo);
        //7.返回给前端
        return map;
    }

    /**
     * 微信扫码登录
     * 获取code，请求第二个接口
     *
     * @param dto
     * @return
     */
    @Override
    public AjaxResult wechat(WeChatDto dto) {
        String strJSON = HttpUtil.httpGet(String.format(LoginConst.SECOND_URL, LoginConst.WX_APP_ID, LoginConst.WX_SECRET, dto.getCode()));
        if (null == strJSON) {
            log.info("调用微信登陆第二个接口失败！");
            throw new BussinessException("网络错误！");
        }
        //将JSON字符串格式转化为JSON格式
        JSONObject jsonObject = JSONObject.parseObject(strJSON);
        String token = jsonObject.getString("access_token");
        if (StringUtils.isBlank(token)) {
            log.info("调用微信登陆第二个接口失败，获取token失败");
            throw new BussinessException("网络错误！");
        }
        String openid = jsonObject.getString("openid");
        //判单wxuser表中是否有该数据，有则直接登录，没有返回noBinder
        Wxuser wxuser = wxuserMapper.findByopenId(openid);
        if (null != wxuser && null != wxuser.getUserId()) {
            Logininfo logininfo = logininfoMapper.findByUserId(wxuser.getUserId());
            HashMap<String, Object> map = getMap(logininfo);
            return AjaxResult.me().success(map);
        }
        StringBuilder sb = new StringBuilder();
        sb.append("?accessToken=").append(token).append("&openId=").append(openid);
        return AjaxResult.me().fail("noBinder").setResultObj(sb.toString());
    }

    /**
     * 微信扫码绑定手机
     *
     * @param dto
     * @return
     */
    @Override
    public HashMap<String, Object> phoneBinder(BinderDto dto) {
        //1.校验参数
        String binderSmsCodeRedis = (String) redisTemplate.opsForValue().get(LoginConst.BINDER_PHONE_KEY + dto.getPhone());
        if (null == binderSmsCodeRedis || !binderSmsCodeRedis.equals(dto.getVerifyCode())) {
            throw new BussinessException("手机验证码错误");
        }
        //请求第三个接口获取 wxuser信息
        String str = HttpUtil.httpGet(String.format(LoginConst.THIRD_URL, dto.getAccessToken(), dto.getOpenId()));
        Wxuser wxuser = JSONObject.parseObject(str, Wxuser.class);
        if (null == wxuser) {
            log.info("调用微信扫描登录第三个接口失败！");
            throw new BussinessException("网络异常，请稍后再试！");
        }
        //判断该电话是否已经登录过
        User user = userMapper.findByPhone(dto.getPhone());
        Logininfo logininfo = new Logininfo();
        if (null == user) {
            //没有登录过，保存三张表
            //添加登录信息 loginingo
            BeanUtils.copyProperties(dto.getUser(), logininfo);
            logininfo.setType(1);
            logininfo.setDisable(1);
            logininfoMapper.add(logininfo);
            user = dto.getUser();
            user.setLogininfoId(logininfo.getId());
            userMapper.add(user);
            //给用户发送邮件：默认密码123456
            EmailUtil.sendPasswordEmail();
            //添加wxuser
        } else {
            //登录过，找到登录信息
            logininfo = logininfoMapper.findByUserId(user.getId());
        }
        //登录过保存一张表
        //添加wxuser
        wxuser.setUserId(user.getId());
        wxuserMapper.add(wxuser);
        //实现登录
//        LoginVo loginVo = getLoginVo(logininfo);

        return getMap(logininfo);
    }


    /**
     * 无状态登录 redis token
     *
     * @param logininfo
     * @return
     */
//    private LoginVo getLoginVo(Logininfo logininfo) {
//        //生成UUID保存登录信息
//        String token = UUID.randomUUID().toString();
//        redisTemplate.opsForValue().set(token, logininfo,30, TimeUnit.MINUTES);
//        //5.将用户敏感信息置空
//        logininfo.setSalt("");
//        logininfo.setPassword("");
//        logininfo.setPhone("");
//        logininfo.setEmail("");
//        //6.将token和logininfo置到loginVo中，返回给前端
//        LoginVo loginVo = new LoginVo();
//        loginVo.setToken(token);
//        loginVo.setLogininfo(logininfo);
//        return loginVo;
//    }
//    private LoginVo getLoginVo(Logininfo logininfo) {
//        LoginVo loginVo = new LoginVo();
//        try {
//            UserInfo userInfo = new UserInfo();
//            //只有管理员才有才需要设置菜单信息、权限信息
//            if (logininfo.getType() == 0) {
//                // 2.1.获取用户权限信息，我们只需要sn权限编码即可
//                List<String> permissions = permissionMapper.findAllByLogininfoId(logininfo.getId());
//                // 2.2.获取用户菜单信息
//                List<Menu> menus = menuMapper.findAllByLogininfoId(logininfo.getId());
//                // 2.3.将菜单信息，权限信息放入到userinfo对象中
//                loginVo.setPermissions(permissions);
//                loginVo.setMenus(menus);
//                //---------给后端拦截器用
//                userInfo.setPermissions(permissions);
//                userInfo.setMenus(menus);
//            }
//            // 3.根据密钥文件名称获取到密钥
//            PrivateKey privateKey = RsaUtils.getPrivateKey(this.getClass().getClassLoader().getResource(jwtRsaPri).getFile());
//            // 4.根据密钥、用户信息、获取JWT加密串
//            String token = JwtUtils.generateTokenExpireInMinutes(loginVo, privateKey, 30000);
//            //5.将用户敏感信息置空
//            logininfo.setSalt("");
//            logininfo.setPassword("");
//            logininfo.setPhone("");
//            logininfo.setEmail("");
//            //6.将token和logininfo置到loginVo中，返回给前端
//            loginVo.setToken(token);
//            loginVo.setLogininfo(logininfo);
//            //--------给后端拦截器用
//            userInfo.setLogininfo(logininfo);
//        } catch (Exception e) {
//            throw new RuntimeException(e);
//        }
//        return loginVo;
//    }

    /**
     * @Title: loginSuccessJwtHandler
     * @Description: 用户登录成功后生成JWT加密串并放入到map对象中
     * @Author: Mr.She
     * @Version: 1.0
     * @Date:  2022/6/30 13:23
     * @Parameters: [cn.itsource.user.domain.Logininfo]
     * @Return java.util.Map<java.lang.String,java.lang.Object>
     */
    public HashMap<String, Object> getMap(Logininfo logininfo){
        // 1.创建UserInfo对象，存储logininfo对象，并用于后续赋值
        HashMap<String, Object> map = new HashMap<>();;
        try {
            UserInfo userInfo = new UserInfo();
            userInfo.setLogininfo(logininfo);
            // 2.判断是后台用户登录才需要设置菜单信息、权限信息，类型为0那么就是后台用户登录
            if(logininfo.getType() == 0){
                // 2.1.获取用户权限信息，我们只需要sn权限编码即可
                List<String> permissions = permissionMapper.findAllByLogininfoId(logininfo.getId());
                // 2.2.获取用户菜单信息
                List<Menu> menus = menuMapper.findAllByLogininfoId(logininfo.getId());
                // 2.3.将菜单信息，权限信息放入到userinfo对象中
                userInfo.setPermissions(permissions);
                userInfo.setMenus(menus);
                //2.4 将店铺信息存入userinfo中的shop
                // todo 后台用户 有可能是店铺员工
                Employee employee = employeeMapper.findByLogininfoId(logininfo.getId());
                if (null != employee && null != employee.getShopId()) {
                    Shop shop = shopMapper.findById(employee.getShopId());
                    userInfo.setShop(shop);
                }
            }
            // 3.根据密钥文件名称获取到密钥
            PrivateKey privateKey = RsaUtils.getPrivateKey(this.getClass().getClassLoader().getResource(jwtRsaPri).getFile());
            // 4.根据密钥、用户信息、获取JWT加密串
            String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 9999);
            // 5.按照正常逻辑我们直接返回token给前端即可，但是那样前端需要解密，我们使用简化版本JWT，还是返回用户信息
            // 封装token、用户信息、权限信息、菜单信息给前端
            map.put("token", token);
            map.put("logininfo", logininfo);
            map.put("permissions", userInfo.getPermissions());
            map.put("menus", userInfo.getMenus()                                                                                                                                             );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return map;
    }

}
